﻿using System;
using System.Drawing;
using System.Windows.Forms;

namespace ConnectedComponents
{
    public class Field
    {
        #region Size and initialization

        public Field() : this(0.5, 5)
        {
        }

        public Field(double whitePercentage, int fieldSize)
        {
            WhitePercentage = whitePercentage;
            Size = new Point(fieldSize, fieldSize);
        }

        public double WhitePercentage { get; set; }

        private Point mSize;
        public Point Size
        {
            get
            {
                return mSize;
            }
            set
            {
                if (value.X <= 0 || value.Y <= 0)
                {
                    return;
                }
                mSize = value;
                InitializeField();
            }
        }

        Color[,] field;

        public Color this[int x, int y]
        {
            get
            {
                return field[x, y];
            }
            set
            {
                field[x, y] = value;
            }
        }

        void InitializeField()
        {
            field = new Color[Size.X, Size.Y];
            for (int i = 0; i < Size.X; i++)
            {
                for (int j = 0; j < Size.Y; j++)
                {
                    this[i, j] = GetRandomElement();
                }
            }
        }

        static Random random = new Random();

        Color GetRandomElement()
        {
            return random.Next(100) < 100 * WhitePercentage ? Color.White : Color.Black;
        }

        public static Color GetRandomColor()
        {
            return Color.FromArgb(
                random.Next(0, 256),
                random.Next(0, 256),
                random.Next(0, 256));
        }

        #endregion

        #region Drawing

        public void Draw(Control form1, Graphics graphics)
        {
            int width = form1.ClientSize.Width;
            int heigth = form1.ClientSize.Height;

            for (int i = 0; i < Size.X; i++)
            {
                for (int j = 0; j < Size.Y; j++)
                {
                    float x = width * (float)i / Size.X;
                    float y = heigth * (float)j / Size.Y;
                    using (Brush brush = new SolidBrush(this[i, j]))
                        graphics.FillRectangle(
                            brush,
                            x,
                            y,
                            width * (float)(i + 1) / Size.X - x,
                            heigth * (float)(j + 1) / Size.Y - y
                            );
                }
            }
        }

        #endregion

        #region Find connected components algorithm

        /// <summary>
        /// This is the main algorithm.
        /// </summary>
        public ConnectedComponentSet FindConnectedComponents()
        {
            ConnectedComponentSet result = new ConnectedComponentSet();
            ConnectedComponentSet current = new ConnectedComponentSet();

            // we scan the entire matrix line by line
            for (int y = 0; y < Size.Y; y++)
            {
                SpanList spansOnCurrentLine = GetSpansFromLine(y);
                foreach (var span in spansOnCurrentLine)
                {
                    PlaceSpan(span, current);
                }

                ConnectedComponentSet complete = new ConnectedComponentSet();

                foreach (var component in current)
                {
                    component.Spans.UnionWith(component.FrontalSpans);
                    if (component.NewFrontalSpans.Count == 0)
                    {
                        complete.Add(component);
                    }
                    else
                    {
                        component.FrontalSpans = component.NewFrontalSpans;
                        component.NewFrontalSpans = new SpanList();
                    }
                }

                foreach (var component in complete)
                {
                    component.FrontalSpans.Clear();
                    current.Remove(component);
                    result.Add(component);
                }
            }

            foreach (var component in current)
            {
                component.Spans.UnionWith(component.FrontalSpans);
                component.Spans.UnionWith(component.NewFrontalSpans);
                result.Add(component);
            }

            return result;
        }

        void PlaceSpan(Span currentSpan, ConnectedComponentSet currentComponentSet)
        {
            if (currentSpan.Y == 0)
            {
                currentComponentSet.AddNewComponentFromSpan(currentSpan);
                return;
            }

            ConnectedComponentSet componentsToJoin = new ConnectedComponentSet();
            foreach (var component in currentComponentSet)
            {
                if (component.FrontalSpans.IntersectsWith(currentSpan))
                {
                    componentsToJoin.Add(component);
                }
            }

            if (componentsToJoin.Count == 0)
            {
                currentComponentSet.AddNewComponentFromSpan(currentSpan);
            }
            else if (componentsToJoin.Count == 1)
            {
                componentsToJoin[0].NewFrontalSpans.Add(currentSpan);
            }
            else
            {
                for (int i = 1; i < componentsToJoin.Count; i++)
                {
                    componentsToJoin[0].NewFrontalSpans.Add(currentSpan);
                    componentsToJoin[0].UnionWith(componentsToJoin[i]);
                    currentComponentSet.Remove(componentsToJoin[i]);
                }
            }
        }

        SpanList GetSpansFromLine(int y)
        {
            SpanList result = new SpanList();
            Color currentElement = this[0, y];
            Span current = new Span() { StartX = 0, Y = y, Color = currentElement };
            
            for (int x = 1; x < Size.X; x++)
            {
                if (this[x, y] != currentElement)
                {
                    currentElement = this[x, y];
                    current.EndX = x - 1;
                    result.Add(current);
                    current = new Span() { StartX = x, Y = y, Color = currentElement };
                }
            }
            
            current.EndX = Size.X - 1;
            result.Add(current);
            return result;
        }

        #endregion
    }
}
